package asms;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class LoaderVisitor extends ClassVisitor implements Opcodes {
    public LoaderVisitor(ClassVisitor classVisitor){
        super(Opcodes.ASM5, classVisitor);
    }
    public LoaderVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
    }

    /**
     * 监控方法
     * 当ClassReader扫描源码发现method的时候即会自动调用该方法。
     * 我们可以在该方法中通过MethodVisitor对方法的字节码进行修改。
     * 同样，demo中我们只修改指定方法controller.Load.sayHello()。
     * access: 方法的access(想想反射)
     * name: 方法名
     * descriptor: 方法描述符
     * signature: 参数描述符
     * exceptions: 异常
     * */
    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor methodVisitor = cv.visitMethod(access, name, descriptor, signature, exceptions);
        // 对index方法做修改
        if(name.equals("sayHello")){
            return new LoaderMethodVisitor(methodVisitor);
        }
        return methodVisitor;
    }
}

class LoaderMethodVisitor extends MethodVisitor implements Opcodes{
    public LoaderMethodVisitor(MethodVisitor methodVisitor){
        super(Opcodes.ASM5, methodVisitor);
    }

    public LoaderMethodVisitor(int api, MethodVisitor methodVisitor) {
        super(api, methodVisitor);
    }

    /**
     * 扫描代码段，也即是代码开始部分
     * 在该处修改字节码，会将内容添加进方法的开头部分。
     * */
    @Override
    public void visitCode() {
        super.visitCode();
        modifyCode();
        System.out.println("This method is visitCode");
    }
    /**
     * 形如Ljava/io/PrintStream;与(Ljava/lang/String;)V都是描述符，
     * 前者为类型描述符说明是一个java.io.PrintStream类型；
     * 后者为方法描述符，方法描述符的括号中为每个形参的类型描述符，最后跟返回值类型：
     * V: 代表返回值为void
     * 参考链接：
     * http://www.jiajiajia.club/blog/artical/yjw520/145
     * */
    public void modifyCode(){
        /**
         * 要调用System.out.println需要先获取一个System.out
         * out为java.lang.System的一个属性，所以需要先调用MethodVisitor.visitFieldInsn()
         * 获取一个out(属性名out，属性类型为java.io.PrintStream所以描述符为Ljava/io/PrintStream;)。
         * */
        mv.visitFieldInsn(Opcodes.GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");
        /**
         * 通过上面的操作，已经获取到了一个out属性，要调用out的println需要先将参数值压栈
         * 也即是MethodVisitor.visitLdcInsn()
         * 然后再把调用方法写进去即可。
         * */
        mv.visitLdcInsn("4ut15m is here.");
        mv.visitMethodInsn(INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V",false);
    }

    /**
     * 代码结束部分
     * */
    @Override
    public void visitInsn(int opcode) {
        /**
         * 当扫描到方法代码块即将结束，也即是return或者抛出异常之前进入下述部分
         * 这部分添加代码会加到方法结束前的部分。
         * */
        if((opcode>=IRETURN&&opcode<=RETURN)||opcode==ATHROW){
            mv.visitFieldInsn(GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");
            mv.visitLdcInsn("this method is end");
            mv.visitMethodInsn(INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V",false);
        }

        super.visitInsn(opcode);
    }


}
